package demo.javax.sql;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DataSourceClosedException;
import com.alibaba.druid.pool.DruidDataSource;
import com.zaxxer.hikari.HikariDataSource;
import demo.db.mysql.MySqlDemo;
import demo.java.lang.ThreadDemo;
import demo.java.sql.ConnectionDemo;
import demo.java.sql.JdbcDemo;
import org.junit.Test;

import java.sql.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * DRUID是阿里巴巴开源平台上一个数据库连接池实现，它结合了C3P0、DBCP、PROXOOL等DB池的优点，
 * 同时加入了日志监控，可以很好的监控DB池连接和SQL的执行情况，
 * 可以说是针对监控而生的DB连接池(据说是目前最好的连接池,不知道速度有没有BoneCP快)。
 */
public class DruidDataSourceDemo {


    @Test
    public void testDruidDataSource() throws SQLException {
        String jdbcUrl = "jdbc:mysql://localhost:3306/ops_alert?useSSL=false&serverTimezone=UTC";
        String username = "root";
        String password = "rootroot";

        DruidDataSource ds = initDataSource(jdbcUrl, username, password);
        try (Connection conn = ds.getConnection();
             Statement statement = conn.createStatement();) {
            // 执行SQL语句
            ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
            JdbcDemo.printResultSet(resultSet);
        } catch (Exception e) {
            e.printStackTrace();
        }

        ds.close();
    }


    @Test
    public void testChangeJdbcUrl() throws SQLException {
        String jdbcUrl = "jdbc:mysql://localhost:3306/ops_alert?useSSL=false&serverTimezone=UTC";
        String username = "root";
        String password = "rootroot";

        DruidDataSource ds = initDataSource(jdbcUrl, username, password);
        int i = 0;
        while (i < 100) {
            try (Connection conn = ds.getConnection()) {
                // 执行SQL语句
                Statement statement = conn.createStatement();
                ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
                JdbcDemo.printResultSet(resultSet);
                printPoolStatus(ds);
            } catch (Exception e) {
                e.printStackTrace();
            }
            i++;
            if (i == 50) {
                System.err.println("------------------ change jdbcUrl to " + jdbcUrl);
                jdbcUrl = "jdbc:mysql://cex-mysql-ex-test-cluster.cluster-c5mgk4qm8m2z.ap-southeast-1.rds.amazonaws.com:3358/ops_alert";
                username = "ops_arthas_rw";
                password = "VYUmItYvMaQ2Tzdn";
                try {
                    ds.setUrl(jdbcUrl);
                    ds.setUsername(username);
                    ds.setPassword(password);
                } catch (UnsupportedOperationException e) {
                    System.err.println("change jdbcUrl failed, UnsupportedOperationException: " + e.getMessage());
                }
                ds.setMaxActive(50);
            }
            ThreadDemo.safeSleep(300);
        }

    }


    /**
     * 测试1、DS关闭后，已获取的连接是否可用。
     * 结论：
     * 1. DS关闭后，已获取的连接正常提交SQL语句。
     *
     * @throws SQLException
     */
    @Test
    public void testCloseDataSource1() throws SQLException {
        String jdbcUrl = "jdbc:mysql://localhost:3306/ops_alert?useSSL=false&serverTimezone=UTC";
        String username = "root";
        String password = "rootroot";

        DruidDataSource ds = initDataSource(jdbcUrl, username, password);
        // 打印连接池状态
        startPrintPoolStatsAsync(ds);
        System.out.println("===1. 正常获取连接，执行查询语句");
        // 执行SQL语句
        try (Connection conn = ds.getConnection();
             Statement statement = conn.createStatement();) {
            // 执行SQL语句
            ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
            JdbcDemo.printResultSet(resultSet);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("===2. 先获取连接,再关闭连接池，执行查询语句");
        // 获取连接
        Connection conn = null;
        Statement statement = null;
        try {
            conn = ds.getConnection();
            statement = conn.createStatement();
            ds.close();
            System.out.println("===3. 关闭连接池");
            ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
            JdbcDemo.printResultSet(resultSet);
        } finally {
            try {
                statement.close();
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        try {
            System.out.println("===4. 再次执行SQL");
            ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
            JdbcDemo.printResultSet(resultSet);
        } catch (SQLNonTransientConnectionException e) {
            e.printStackTrace();
        }

        ThreadDemo.safeSleep(1000);

    }


    /**
     * 测试2、验证DS关闭后，能否再次从连接池获取连接。
     * 结论：
     * 1. DS关闭后，再次从连接池获取连接，会抛出DataSourceClosedException异常：dataSource already closed at xxx。
     *
     * @throws SQLException
     */
    @Test
    public void testCloseDataSource2() throws SQLException {
        String jdbcUrl = "jdbc:mysql://localhost:3306/ops_alert?useSSL=false&serverTimezone=UTC";
        String username = "root";
        String password = "rootroot";

        DruidDataSource ds = initDataSource(jdbcUrl, username, password);
        // 打印连接池状态
        startPrintPoolStatsAsync(ds);
        System.out.println("====================1. 正常获取连接，执行查询语句");
        // 执行SQL语句
        try (Connection conn = ds.getConnection();
             Statement statement = conn.createStatement();) {
            // 执行SQL语句
            ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
            JdbcDemo.printResultSet(resultSet);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 关闭连接池
        System.out.println("===2. 关闭连接池");
        ds.close();

        System.out.println("===  再次从连接池获取连接，执行查询语句");
        try {
            Connection conn = ds.getConnection();
            ConnectionDemo.testConnection(conn);
        } catch (DataSourceClosedException e) {
            e.printStackTrace();
        }


        ThreadDemo.safeSleep(1000);

    }

    /**
     * 初始化数据源
     *
     * @param jdbcUrl
     * @param username
     * @param password
     * @return
     * @throws SQLException
     */
    public static DruidDataSource initDataSource(String jdbcUrl, String username, String password)
            throws SQLException {
        DruidDataSource ds = new DruidDataSource();
        /* 基本连接信息 */
        ds.setName("Druid-DS");
        ds.setDriverClassName(MySqlDemo.JDBC_DRIVER_V8);
        ds.setUrl(jdbcUrl);
        ds.setUsername(username);
        ds.setPassword(password);

        // 连接池配置
        ds.setInitialSize(5);
        ds.setMinIdle(5);
        ds.setMaxActive(10);
        ds.setMaxWait(2000);

        // 连接有效性检查配置
        /**
         * 配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位是毫秒. 有两个含义：
         * <li> 1) Destroy线程会检测连接的间隔时间
         * <li>2) testWhileIdle的判断依据，详细看testWhileIdle属性的说明
         */
        ds.setTimeBetweenEvictionRunsMillis(60000);
        /* 配置一个连接在池中最小生存的时间，单位是毫秒 */
        ds.setMinEvictableIdleTimeMillis(300_000);
        /*
         * 验证连接有效与否的SQL，不同的数据配置不同
         * .如果validationQuery为null，testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
         */
        ds.setValidationQuery("select 1");
        ds.setTestWhileIdle(true);
        /* 这里建议配置为TRUE，防止取到的连接不可用 */
        ds.setTestOnBorrow(true);
        ds.setTestOnReturn(false);

        // 连接泄漏检测配置
        ds.setRemoveAbandoned(true);                    // 是否移除泄露的连接
        ds.setRemoveAbandonedTimeout(180);              // 泄露连接的超时时间（秒）
        ds.setLogAbandoned(true);                       // 是否记录泄露连接的日志

        /* 物理连接初始化的时候执行的sql */
        ds.setConnectionInitSqls(null);
        /*
         * 打开PSCache，并且指定每个连接上PSCache的大小.PSCache对支持游标的数据库性能提升巨大，比如说oracle。在mysql下建议关闭。
         */
        ds.setPoolPreparedStatements(true);
        /*
         * 要启用PSCache，必须配置大于0，当大于0时，poolPreparedStatements自动触发修改为true。
         * 在Druid中，不会存在Oracle下PSCache占用内存过多的问题，可以把这个数值配置大一些，比如说100
         */
        ds.setMaxPoolPreparedStatementPerConnectionSize(20);
        /* 这里配置提交方式，默认就是TRUE，可以不用配置 */
        ds.setDefaultAutoCommit(true);

        /*
         * 属性类型是字符串，通过别名的方式配置扩展插件，常用的插件有：
         * 监控统计用的filter:stat;日志用的filter:log4j;防御sql注入的filter:wall
         */
        // 过滤器配置（监控和统计）
        try {
            ds.setFilters("stat,wall");                 // 配置监控统计和防火墙过滤器
        } catch (Exception e) {
            System.err.println("配置过滤器失败: " + e.getMessage());
        }
        List<Filter> filters = Arrays.asList();
        /*
         * 类型是List<com.alibaba.druid.filter.Filter>，如果同时配置了filters和proxyFilters，是组合关系，
         * 并非替换关系
         */
        ds.setProxyFilters(filters);
        return ds;
    }


    /**
     * 打印连接池状态
     */
    private static void printPoolStatus(DruidDataSource dataSource) {
        System.out.println("\n=== 连接池状态 ===");
        // 连接池配置信息
        Map<String, Object> cfg = new HashMap<>();
        cfg.put("InitialSize", dataSource.getInitialSize());
        cfg.put("MinIdle", dataSource.getMinIdle());
        cfg.put("MaxActive", dataSource.getMaxActive());
        cfg.put("MaxWait", dataSource.getMaxWait());
        System.out.println("连接池配置信息: " + cfg);


        // 连接池实时状态
        Map<String, Object> status = new HashMap<>();
        status.put("Active", dataSource.getActiveCount());
        status.put("PoolingCount", dataSource.getPoolingCount());
        status.put("CreateCount", dataSource.getCreateCount());
        status.put("DestroyCount", dataSource.getDestroyCount());
        status.put("ConnectCount", dataSource.getConnectCount());
        status.put("CloseCount", dataSource.getCloseCount());
        status.put("ActivePeak", dataSource.getActivePeak());
        status.put("PoolingPeak", dataSource.getPoolingPeak());
        status.put("WaitThreadCount", dataSource.getWaitThreadCount());

        System.out.println("连接池状态: " + status);
    }


    public static void startPrintPoolStatsAsync(DruidDataSource dataSource) {
        Thread t = new Thread(
                () -> {
                    while (true) {
                        printPoolStatus(dataSource);
                        ThreadDemo.safeSleep(200);
                    }
                });
        t.setDaemon(true);
        t.start();
    }


    /**
     * 关闭连接池，会断开所有空闲连接，并终止或关闭所有活动连接。
     *
     * @param dataSource
     */
    public static void closeDsAsync(DruidDataSource dataSource) {
        Thread t = new Thread(
                () -> {
                    dataSource.close();
                    System.out.println("====关闭连接池，会断开所有空闲连接，并终止或关闭所有活动连接");
                });
        t.start();
    }

}
